/*
    This file is part of open-wars <http://code.google.com/p/open-wars/>.

    open-wars is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    open-wars is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with open-wars.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <opengl-renderer.h>

OpenGLRenderer *masterOpenGLRenderer;

// Window Callbacks
IRendererCallback onWindowCreated;
IRendererCallback onBeforeWindowClosed;
IRendererCallback onAfterWindowClosed;
IRendererCallback onWindowResized;
IRendererCallback onWindowMinimized;
IRendererCallback onWindowRestored;
IRendererCallback onWindowLostFocus;
IRendererCallback onWindowGainedFocus;
IRendererCallback onWindowDraw;

// Texture Callbacks
IRendererCallback  onTextureLoaded;
IRendererCallback  onTextureLoadFailure;
IRendererCallback  onBeforeTextureFreed;
IRendererCallback  onAfterTextureFreed;


static void OpenGLRenderer_renderScene(void)
{
	masterOpenGLRenderer->renderScene();
}

static void OpenGLRenderer_onIdle(void)
{
    int lastTime = glutGet(GLUT_ELAPSED_TIME);
    BOOST_FOREACH(IRendererCallback it, masterOpenGLRenderer->OnIdle)
        (*it)(boost::any(lastTime));
}

OpenGLRenderer::OpenGLRenderer(int argc, char** argv)
{
    masterOpenGLRenderer = this;
	isFullScreen = false;
	resolutionInfo = new _IRendererResResult;
	
	onWindowCreated 		= 0;
	onBeforeWindowClosed 	= 0;
	onAfterWindowClosed 	= 0;
	onWindowResized 		= 0;
	onWindowMinimized 		= 0;
	onWindowRestored 		= 0;
	onWindowLostFocus 		= 0;
	onWindowGainedFocus 	= 0;
	onWindowDraw            = 0;
	
	onTextureLoaded 		= 0;
	onTextureLoadFailure    = 0;
	onBeforeTextureFreed 	= 0;
	onAfterTextureFreed 	= 0;

    OnIdle.clear();

	glutInit(&argc, argv);
}

OpenGLRenderer::~OpenGLRenderer()
{
	delete resolutionInfo;
}

void OpenGLRenderer::renderScene(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity(); 

	
	if (onWindowDraw)
		(*onWindowDraw)(NULL);
	
	glutSwapBuffers();
}

void OpenGLRenderer::Repaint(void)
{
    glutPostRedisplay();
}

void OpenGLRenderer::InitializeWindow(NUMBER width, NUMBER height, NUMBER depth)
{
	GLenum type = GLUT_RGBA | GLUT_DOUBLE;
	
	glutInitDisplayMode(type);
	glutInitWindowSize((int)width, (int)height);
	glutWindowID = glutCreateWindow("OpenGL");
	glutDisplayFunc(OpenGLRenderer_renderScene);
    glutIdleFunc(OpenGLRenderer_onIdle);
	resolutionInfo->width = width;
	resolutionInfo->height = height;
	resolutionInfo->depth = depth;
	
	glViewport(0,0,width, height);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, width, height, 0, -100,100);
	glMatrixMode(GL_MODELVIEW);


	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
	glEnable(GL_ALPHA_TEST);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);
	
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    
    glutSetCursor(GLUT_CURSOR_NONE);
    
    glShadeModel(GL_FLAT);
	
	if (onWindowCreated)
		(*onWindowCreated)(NULL);
}

void OpenGLRenderer::DestroyWindow(void)
{
	if (onBeforeWindowClosed)
		(*onBeforeWindowClosed)(NULL);
		
	glutDestroyWindow(glutWindowID);
		
	if (onAfterWindowClosed)
		(*onAfterWindowClosed)(NULL);
}

void OpenGLRenderer::SetWindowResolution (NUMBER width, NUMBER height, NUMBER depth)
{
	glutReshapeWindow((int)width, (int)height);
	
	if (isFullScreen)
		glutFullScreen();
	
	resolutionInfo->width = width;
	resolutionInfo->height = height;
	resolutionInfo->depth = depth;
	
	if (onWindowResized)
		(*onWindowResized)(NULL);
}

IRendererResResult OpenGLRenderer::GetWindowResolution(void)
{
	return resolutionInfo;
}

void OpenGLRenderer::SetFullScreen(BOOLEAN fullScreen)
{
	if (fullScreen && (!isFullScreen))
		glutFullScreen();
	else if ((!fullScreen) && isFullScreen)
		glutReshapeWindow((int)resolutionInfo->width, (int)resolutionInfo->height);
		
	isFullScreen = fullScreen;

}

void OpenGLRenderer::SetWindowCaption(STRING caption)
{
	glutSetWindowTitle(caption.c_str());
}

int OpenGLRenderer::GetTextureInfo(int ColourType)
{
   int ret;
   switch(ColourType)
   {
      case PNG_COLOR_TYPE_GRAY:
         ret = 1;
      break;
      case PNG_COLOR_TYPE_GRAY_ALPHA:
         ret = 2;
      break;
      case PNG_COLOR_TYPE_RGB:
         ret = 3;
      break;
      case PNG_COLOR_TYPE_RGB_ALPHA:
         ret = 4;
      break;
      default:
         ret = -1;
   };
   return ret;
};

TEXTURE OpenGLRenderer::LoadTextureFromFile(STRING fileName)
{
	GLuint texture;
	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;
	png_bytep *row_pointers = NULL;
	int bitDepth, colourType;
	
	FILE *pngFile = fopen(fileName.c_str(), "rb");
	
	if(!pngFile)
	return 0;
	
	png_byte sig[8];
	
	fread(&sig, 8, sizeof(png_byte), pngFile);
	rewind(pngFile);
	if(!png_check_sig(sig, 8))
		return 0;
	
	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,NULL,NULL);
	
	if(!png_ptr)
		return 0;
	
	if(setjmp(png_jmpbuf(png_ptr)))
		return 0;
	
	info_ptr = png_create_info_struct(png_ptr);
	
	if(!info_ptr)
		return 0;
	
	png_init_io(png_ptr, pngFile);
	
	png_read_info(png_ptr, info_ptr);
	
	bitDepth = png_get_bit_depth(png_ptr, info_ptr);
	
	colourType = png_get_color_type(png_ptr, info_ptr);
	
	if(colourType == PNG_COLOR_TYPE_PALETTE)
	png_set_palette_to_rgb(png_ptr);
	
	if(png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
		png_set_tRNS_to_alpha(png_ptr);
	
	if(bitDepth == 16)
		png_set_strip_16(png_ptr);
	else if(bitDepth < 8)
		png_set_packing(png_ptr);
	
	png_read_update_info(png_ptr, info_ptr);
	
	png_uint_32 width, height;
	png_get_IHDR(png_ptr, info_ptr, &width, &height,
		 &bitDepth, &colourType, NULL, NULL, NULL);
	
	int components = GetTextureInfo(colourType);
	
	if(components == -1)
	{
		if(png_ptr)
			png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
		return 0;
	}
	
	GLubyte *pixels = (GLubyte *)malloc(sizeof(GLubyte) * (width * height * components));
	
	row_pointers = (png_bytep *)malloc(sizeof(png_bytep) * height);
	
	for(int i = 0; i < (int)height; ++i)
		row_pointers[i] = (png_bytep)(pixels + (i * width * components));
	
	png_read_image(png_ptr, row_pointers);
	png_read_end(png_ptr, NULL);


	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	
	GLuint glcolours;
	(components==4) ? (glcolours = GL_RGBA): (0);
	(components==3) ? (glcolours = GL_RGB): (0);
	(components==2) ? (glcolours = GL_LUMINANCE_ALPHA): (0);
	(components==1) ? (glcolours = GL_LUMINANCE): (0);
	
	glTexImage2D(GL_TEXTURE_2D, 0, components, width, height, 0, glcolours, GL_UNSIGNED_BYTE, pixels);
	
	png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
	
	fclose(pngFile);
	free(row_pointers);
	free(pixels);
	
	GLuint *tex = new GLuint();
	tex[0] = texture;
	return (void*)tex;

}

void OpenGLRenderer::DrawTexture(TEXTURE texture, float x, float y, int width, int height)
{
	glBindTexture(GL_TEXTURE_2D, ((GLuint*)texture)[0]);
	glColor4f(1.0f,1.0f,1.0f,1.0f);
	glPushMatrix();  // Save modelview matrix
	glTranslatef(x,y,0.0f);  // Position sprite	
	glBegin(GL_QUADS);                                   // Draw sprite 
	glTexCoord2f(0.0f,1.0f); glVertex3i(0, height, 0);
	glTexCoord2f(0.0f,0.0f); glVertex3i(0, 0,0);
	glTexCoord2f(1.0f,0.0f); glVertex3i(width, 0, 0);
	glTexCoord2f(1.0f,1.0f); glVertex3i(width, height,0);
	glEnd();
	glPopMatrix();  // Restore modelview matrix

}

void OpenGLRenderer::DrawLine(float x1, float y1, float x2, float y2, float w, int r, int g, int b, int a)
{
	glDisable(GL_TEXTURE_2D); 
	glColor4ub( r, g, b, a); 
	glLineWidth(w); 
	glPushMatrix();
	glTranslatef(x1, y1, 0.0f);
	glBegin(GL_LINES); 
		glVertex2f( 0.0f, 0.0f); 
		glVertex2f( x2, y2); 
	glEnd(); 
	glPopMatrix();
	glEnable(GL_TEXTURE_2D); 
}

void OpenGLRenderer::DrawRect(float x, float y, float w, float h, int r, int g, int b, int a)
{
	glDisable(GL_TEXTURE_2D);
	glColor4ub(r, g, b, a);
	glPushMatrix();
	glTranslatef(x, y, 0.0f);
	glBegin(GL_QUADS);
		glVertex3f(0, h, 0);
		glVertex3f(0, 0, 0);
		glVertex3f(w, 0, 0);
		glVertex3f(w, h, 0);
	glEnd();
	glPopMatrix();
	glEnable(GL_TEXTURE_2D);
}

DISPLAYLIST OpenGLRenderer::DisplayCreateList(void)
{
	GLuint *dl = new GLuint();
	dl[0] = glGenLists(1);
	return dl;
}

void OpenGLRenderer::DisplayBeginList(DISPLAYLIST displayList)
{
	glNewList(((GLuint*)displayList)[0], GL_COMPILE);
}

void OpenGLRenderer::DisplayEndList(void)
{
	glEndList();
}

void OpenGLRenderer::DisplayDrawList(DISPLAYLIST displayList)
{
	glCallList(((GLuint*)displayList)[0]);
}
void OpenGLRenderer::DisplayTranslate(float x, float y)
{
	glTranslatef(x, y, 0.0f);
}

void OpenGLRenderer::DisplayPushMatrix(void)
{
	glPushMatrix();
}

void OpenGLRenderer::DisplayPopMatrix(void)
{
	glPopMatrix();
}

FONT OpenGLRenderer::LoadFont(STRING fileName, int point)
{
	return loadFont(fileName, point);
}

void OpenGLRenderer::DrawFont(FONT font, float x, float y, int r, int g, int b, int a, STRING t)
{
	glPushMatrix();
	glTranslatef(x, y, 1.0f);
	glColor4ub(r, g, b, a);
	printFont((GLFont)font, t);
	glPopMatrix();
}

void OpenGLRenderer::FreeFont(FONT font)
{

}
